﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Data.Caching;

namespace Vit.Caching
{
    public class VelocityCacheManager : ICacheManager
    {
        private DataCache cache;
        private List<string> keys = new List<string>();

        public VelocityCacheManager()
        {
        }

        public VelocityCacheManager(DataCache cache)
        {
            this.cache = cache;
        }

        public static DataCache CreateCache(string hostName, int cachePort, string cacheHostName, string cacheName, bool useLocalCache)
        {
            DataCacheServerEndpoint endpoint = new DataCacheServerEndpoint(hostName, cachePort, cacheHostName);
            DataCacheFactory fac = new DataCacheFactory(new DataCacheServerEndpoint[] { endpoint }, useLocalCache, useLocalCache);
            return fac.GetCache(cacheName);
        }
     
        #region ICacheManager Members

        public void Initialize(string cacheManagerName)
        {
            cache = CreateCache("localhost", 22233, "DistributedCacheService", cacheManagerName, true);
            Name = cacheManagerName;
        }

        public void Add(string key, object value)
        {
            if(!keys.Contains(key))
                keys.Add(key);
            cache.Add(key, value);
        }

        /// <summary>
        /// Adds the specified entry to the cache.
        /// </summary>
        /// <param name="key">The entry key.</param>
        /// <param name="value">The entry value.</param>
        /// <param name="dependentEntitySets">The list of dependent entity sets.</param>
        /// <param name="slidingExpiration">The sliding expiration - IGNORED.</param>
        /// <param name="absoluteExpiration">The absolute expiration.</param>
        public void Add(string key, object value, IEnumerable<string> dependentEntitySets, TimeSpan slidingExpiration, DateTime absoluteExpiration)
        {
            if (!keys.Contains(key))
                keys.Add(key);
            this.cache.Put(key, value, absoluteExpiration - DateTime.Now, dependentEntitySets.Select(c => new DataCacheTag(c)).ToList());

            foreach (var dep in dependentEntitySets)
            {
                this.CreateRegionIfNeeded(dep);
                this.cache.Put(key, " ", dep);
            }
        }

        public bool Contains(string key)
        {
            return keys.Contains(key);
        }

        public int Count
        {
            get { return keys.Count; }
        }

        public void Clear()
        {
            keys.ForEach(o =>
            {
                RemoveItem(o);
            });
            keys.Clear();
        }

        public object GetData(string key)
        {
            return cache.Get(key);
        }

        public void Remove(string key)
        {
            if (keys.Contains(key))
                keys.Remove(key);
            RemoveItem(key);
        }

        public void RemoveDependancySet(IEnumerable<string> sets)
        {
            // Go through the list of objects in each of the set. 
            foreach (var dep in sets)
            {
                foreach (var val in this.cache.GetObjectsInRegion(dep))
                {
                    this.cache.Remove(val.Key);
                }
            }
        }

        private void RemoveItem(string key)
        {
            DataCacheItem item = this.cache.GetCacheItem(key);
            this.cache.Remove(key);

            // Remove the keys from the region..
            foreach (var tag in item.Tags)
            {
                this.cache.Remove(key, tag.ToString());
            }
        }

        public object this[string key]
        {
            get
            {
                return GetData(key);
            }
        }

        public string Name
        {
            get;
            private set;
        }

        #endregion

        /// <summary>
        /// Creates the region if needed.
        /// </summary>
        /// <param name="regionName">Name of the region.</param>
        private void CreateRegionIfNeeded(string regionName)
        {
            try
            {
                this.cache.CreateRegion(regionName, false);
            }
            catch (DataCacheException de)
            {
                if (de.ErrorCode != DataCacheErrorCode.RegionAlreadyExists)
                {
                    throw;
                }
            }
        }
    }
}
